package com.giscloud.controller;


import cn.hutool.core.date.DateTime;
import com.giscloud.commons.annotation.AuthIgnore;
import com.giscloud.commons.annotation.SSOIgnore;
import com.giscloud.commons.codec.AESUtils;
import com.giscloud.commons.codec.CipherUtils;
import com.giscloud.commons.utils.*;
import com.giscloud.generator.constant.RedisConstant;
import com.giscloud.generator.entity.dto.AmpStaffDTO;
import com.giscloud.generator.entity.dto.LoginDTO;
import com.giscloud.generator.entity.vo.AmpStaffVO;
import com.giscloud.generator.entity.vo.UserIdentityVO;
import com.giscloud.redis.utils.RedisUtils;
import com.giscloud.generator.service.AmpStaffService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Controller;
import org.springframework.util.CollectionUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.ModelAndView;

import javax.annotation.Resource;
import javax.imageio.ImageIO;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.net.URLEncoder;
import java.util.List;
import java.util.UUID;

@Controller
public class LoginController {
    private static final Logger log = LoggerFactory.getLogger(LoginController.class);
    @Resource
    private RedisUtils redisUtils;
    @Resource
    private AmpStaffService ampStaffService;

    /**
     * cookie.login.acct 用户登录信息 cookiename
     */
    @Value("${cookie.login.user}")
    private String cookieLoginUser;
    /**
     * cookie.login.psw cookie 加密/解密 秘钥
     */
    @Value("${cookie.login.psw}")
    private String cookieLoginPassword;

    /**
     * cookie.login.realname 用户昵称
     */
    @Value("${cookie.login.name}")
    private String cookieRealName;
    /**
     * cookie 所在的域
     */
    @Value("${cookie.domain}")
    private String cookieDomain;

    /**
     * cookie 存在的最长时间cookie.domain
     */
    @Value("${cookie.maxage}")
    private String cookieMaxAge;
    /**
     * 验证码key
     */
    @Value("${cookie.valid.key}")
    private String validateCodeKey;
    /**
     * 登录失败次数
     */
    @Value("${cookie.login.count: 5}")
    private Long loginCount;

    public static final String USER_ACCT_KEY = "acct";

    public static final String UNIONUUID = "unionuuid";


    @SSOIgnore
    @RequestMapping("/toLogin")
    public ModelAndView toLogin() {
        ModelAndView view = new ModelAndView();
        view.setViewName("login");
        return view;
    }

    @SSOIgnore
    @RequestMapping(value = "/doLogin", method = RequestMethod.POST)
    public ModelAndView userLogin(LoginDTO loginDTO,
                                  HttpServletRequest request, HttpServletResponse response) throws IOException {
        ModelAndView view = new ModelAndView();
        String remoteIp = NetUtils.getRemoteIp(request);
        log.debug("the remote ip is " + remoteIp);
        if (StringUtils.isBlank(loginDTO.getLoginName())) {
            log.error("# 账号不能为空");
            view.addObject("state", -1);
            view.addObject("message", "账号不能为空");
            view.setViewName("login");
            return view;
        }
        if (StringUtils.isBlank(loginDTO.getPassword())) {
            log.error("# 密码不能为空");
            view.addObject("state", -1);
            view.addObject("message", "密码不能为空");
            view.setViewName("login");
            return view;
        }
        String inputValidCode = loginDTO.getValidateCode();
        String validateCode = CookieUtils.getCookieValue(validateCodeKey, request);
        String cachevalidCode = (String) redisUtils.get(validateCode);
        if (StringUtils.isEmpty(inputValidCode)) {
            log.error("验证码为空...");
            view.addObject("state", -1);
            view.addObject("message", "验证码不能为空");
            view.setViewName("login");
            return view;
        }
        if (StringUtils.isEmpty(cachevalidCode)) {
            log.error("验证码已过期...");
            view.addObject("state", -1);
            view.addObject("message", "验证码已过期");
            view.setViewName("login");
            return view;
        }
        if (!inputValidCode.equalsIgnoreCase(cachevalidCode)) {
            log.error("验证码不匹配...");
            view.addObject("state", -1);
            view.addObject("message", "验证码不匹配");
            view.setViewName("login");
            return view;
        }
        AmpStaffDTO ampStaffDTO = new AmpStaffDTO();
        ampStaffDTO.setLoginName(loginDTO.getLoginName());
        AmpStaffVO ampStaffVO = ampStaffService.getAmpStaffByCondition(ampStaffDTO);
        try{
            if (ampStaffVO == null || ampStaffVO.getStatusCd() == 2) {
                log.error("# 账号不存在");
                view.addObject("state", -1);
                view.addObject("message", "账号或者密码错误");
                view.setViewName("login");
                return view;
            }
            if (ampStaffVO.getStatusCd() == 3) {
                log.error("# 账号被禁用");
                view.addObject("state", -1);
                view.addObject("message", "账号或者密码错误");
                view.setViewName("login");
                return view;
            }
            if (RedisConstant.REDIS_LOGIN_LOCK.equals(redisUtils.get(RedisConstant.REDIS_LOGIN_COUNT + loginDTO.getLoginName()))) {
                log.error("# 由于密码输入错误次数大于" + loginCount + "次，帐号已经禁止登录！");
                view.addObject("state", -1);
                view.addObject("message", "账号或者密码输入错误次数过多，请1小时后再试");
                view.setViewName("login");
                return view;
            }
            if (!PasswordUtils.verify(loginDTO.getPassword(), ampStaffVO.getPassword())) {
                //访问一次，计数一次，测试提交
                redisUtils.incr(RedisConstant.REDIS_LOGIN_COUNT + loginDTO.getLoginName(),1L);
                //计数大于5时，设置用户被锁定一小时
                Long remainder = loginCount-Long.parseLong(String.valueOf(redisUtils.get(RedisConstant.REDIS_LOGIN_COUNT + loginDTO.getLoginName())));
                if (remainder <= 0) {
                    redisUtils.set(RedisConstant.REDIS_LOGIN_COUNT + loginDTO.getLoginName(), RedisConstant.REDIS_LOGIN_LOCK, RedisConstant.REDIS_LOCK_TIME_ONE_HOUR);
                }
                if (RedisConstant.REDIS_LOGIN_LOCK.equals(redisUtils.get(RedisConstant.REDIS_LOGIN_COUNT + loginDTO.getLoginName()))) {
                    log.error("# 由于密码输入错误次数大于" + loginCount + "次，帐号已经禁止登录！");
                    view.addObject("state", -1);
                    view.addObject("message", "账号或者密码输入错误次数过多，请1小时后再试");
                    view.setViewName("login");
                    return view;
                }
                log.error("# 密码不正确");
                view.addObject("state", -1);
                view.addObject("message", "账号或者密码错误");
                view.setViewName("login");
                return view;
            }
            log.debug("#账号密码验证通过.....................................");
            redisUtils.del(RedisConstant.REDIS_LOGIN_COUNT + loginDTO.getLoginName());
            UserIdentityVO userIdentityVO = new UserIdentityVO();
            String uuid = UUID.randomUUID().toString();
            redisUtils.set(RedisConstant.REDIS_SSO_ACCOUNT+String.valueOf(ampStaffVO.getStaffId()), uuid, RedisConstant.REDIS_EXPIRE_TIME_ONE_HOUR);//做重复登录判断
            if("admin".equals(ampStaffVO.getLoginName())){
                userIdentityVO.setIsSuperUser(1);
            }
            userIdentityVO.setId(ampStaffVO.getStaffId());
            userIdentityVO.setLoginAccount(ampStaffVO.getLoginName());
            userIdentityVO.setRealName(ampStaffVO.getStaffName());
            userIdentityVO.setUuid(uuid);
            userIdentityVO.setLoginTime(new DateTime());
            userIdentityVO.setLoginIp(remoteIp);
            userIdentityVO.setOrgId(ampStaffVO.getOrgId());
            userIdentityVO.setRolesId(ampStaffVO.getRolesId());
            userIdentityVO.setRoleCode(ampStaffVO.getRoleCode());
            log.error("登录日志：{}",userIdentityVO.toString());
            loginCookie(userIdentityVO, response, request);
            view.setViewName("redirect:/");
        }catch (Exception e){
            log.error(e.getMessage(),e);
            view.addObject("state", -1);
            view.addObject("message", "登录异常");
            view.setViewName("login");
            return view;
        }
        return view;
    }

    @SSOIgnore
    @RequestMapping(value = "/doLogin", method = RequestMethod.GET)
    public ModelAndView userLoginByGetMethod(HttpServletRequest request, HttpServletResponse response) throws IOException {
        ModelAndView view = new ModelAndView();
        // cookie 解密
        String cookieValue = decodeCookie(request);
        if (StringUtils.isNotEmpty(cookieValue)) {
            UserIdentityVO loginUserVo = FastJsonUtils.toBean(cookieValue, UserIdentityVO.class);
            String uuid = (String) redisUtils.get(String.valueOf(loginUserVo.getId()));//重复登录判断
            if (loginUserVo.getUuid() == null || uuid == null) {
                view.addObject("state", -1);
                view.addObject("message", "账号或者密码错误");
                view.setViewName("login");
            } else if (loginUserVo.getUuid().equals(uuid)) {
                String remoteIp = NetUtils.getRemoteIp(request);
                if (loginUserVo.getLoginIp().equals(remoteIp)) {
                    //COOKIE中用户登录IP与当前请求IP一致， 验证通过
                    view.setViewName("redirect:/");
                } else {
                    //COOKIE中用户登录IP与当前请求IP不一致， 验证失败
                    view.addObject("state", -13);
                    view.addObject("message", "您的网络环境已经改变，为了保障安全，请您重新登录！");
                    view.setViewName("login");
                }
            } else {
                view.addObject("state", -14);
                view.addObject("message", "您的账号已在其他地方登录，若不是本人操作，请修改密码！");
                view.setViewName("login");
            }
        }else{
            view.addObject("state", -1);
            view.addObject("message", "");
            view.setViewName("login");
        }
        return view;
    }
    /**
     * Description ： cookie解密
     *
     * @param request
     * @return
     */
    public String decodeCookie(HttpServletRequest request) {
        String loginuserInfoStr = null;
        // cookiename是否失效
        String cookieValue = CookieUtils.getCookieValue(cookieLoginUser, request);
        if (StringUtils.isNotEmpty(cookieValue)) {
            loginuserInfoStr = AESUtils.decrypt2str(cookieValue, cookieLoginPassword);
            if (StringUtils.isEmpty(loginuserInfoStr)) {
                return null;
            }
        } else {
            return null;
        }

        return loginuserInfoStr;
    }
    /**
     * 清除用户登录信息
     *
     * @param name
     * @param domain
     * @param response
     * @param request
     */
    public void clearCookie(String name, String domain, HttpServletResponse response, HttpServletRequest request) {
        String path = "/";
        CookieUtils.deleteCookie(request, response, name, path);
    }

    /**
     * 清除用户登录次数的缓存长度
     *
     * @param id
     */
    public void clearUserLoginRedis(Long id) {
        // 获取 用户登录信息 缓存
        List<Object> list = redisUtils.values(RedisConstant.REDIS_SSO_LOGIN_HISTORY + id);
        if(!CollectionUtils.isEmpty(list)) {
            list.stream().sorted();
            // 总长度大于100 清除历史数据 前50个历史数据
            if (list.size() > 100) {
                int i = 0;
                for (Object str : list) {
                    if (i < 50) {
                        redisUtils.hdel(RedisConstant.REDIS_SSO_LOGIN_HISTORY + id, str);
                    }
                    i++;
                }
            }
        }
    }

    @SSOIgnore
    @AuthIgnore
    @RequestMapping("/logout")
    public ModelAndView toLogout(HttpServletResponse response, HttpServletRequest request) throws Exception {
        clearCookie(cookieRealName, cookieDomain, response, request);
        clearCookie(cookieLoginUser, cookieDomain, response, request);
        clearCookie(USER_ACCT_KEY, cookieDomain, response, request);
        clearCookie(UNIONUUID, cookieDomain, response, request);
        ModelAndView view = new ModelAndView();
        view.addObject("state", 0);
        view.addObject("message", "退出成功");
        view.setViewName("login");
        return view;
    }

    private void loginCookie(UserIdentityVO loginUser, HttpServletResponse response, HttpServletRequest request) {
        try {
            Long id = loginUser.getId();
            // 登录信息存缓存
            clearUserLoginRedis(id);
            redisUtils.hset(RedisConstant.REDIS_SSO_LOGIN_HISTORY + id, loginUser.getLoginTime().toString(), loginUser.getLoginIp());
            // 转化为字符串
            String userInfo = FastJsonUtils.toJSONString(loginUser);
            String path = "/";
            Integer maxAge = Integer.valueOf(cookieMaxAge);
            String domain = cookieDomain;
            // 清除cookie
            clearCookie(cookieRealName, domain, response, request);
            clearCookie(cookieLoginUser, domain, response, request);
            clearCookie(USER_ACCT_KEY, domain, response, request);
            clearCookie(UNIONUUID, domain, response, request);
            // 添加cookie
            // 登录信息
            String cookiesK = cookieLoginUser;
            // 用户姓名
            String cookieReal = cookieRealName;
            CookieUtils.addCookie(response, cookiesK, AESUtils.encrypt(userInfo, cookieLoginPassword), maxAge, path);
            CookieUtils.addCookie(response, USER_ACCT_KEY, loginUser.getLoginAccount(), maxAge, path);
            CookieUtils.addCookie(response, cookieReal, URLEncoder.encode(loginUser.getRealName(), "utf-8"), maxAge, path);
        } catch (Exception e) {
            log.error("loginCookie error:{}" , e);
        }
    }

    /**
     * 获取验证码
     * @param req
     * @param resp
     * @throws Exception
     */
    @SSOIgnore
    @AuthIgnore
    @RequestMapping("/validateCode")
    public void createValidateCode(HttpServletRequest req, HttpServletResponse resp) throws Exception {
        // 获取验证码信息
        ImageCaptcha vCode = new ImageCaptcha(90, 30, 4, 150);

        // 生成redis获取验证码的KEY保存到cookie中
        String validateCode = CipherUtils.MD5Encode(UUID.randomUUID().toString());

        CookieUtils.addCookie(resp, validateCodeKey, validateCode, null, "/");
        // 保存验证码到redis，时间为1分钟
        redisUtils.set(validateCode,  vCode.getCode(),RedisConstant.REDIS_LOCK_TIME_ONE_MINUTES);
        // 禁止图像缓存。
        resp.setHeader("Pragma", "no-cache");
        resp.setHeader("Cache-Control", "no-cache");
        resp.setDateHeader("Expires", 0);
        resp.setContentType("image/jpeg");
        ServletOutputStream sos = null;
        try {
            sos = resp.getOutputStream();
            ImageIO.write(vCode.getBuffImg(), "jpeg", sos);
            sos.flush();
        }catch (Exception e){
            log.error("生成验证码异常：{}",e);
        }finally {
            sos.close();
        }
    }
}
